﻿#pragma once

#include "CartesianCRD.hpp"
#include "GeodeticCRD.hpp"
#include "TopoCentricCRD.hpp"
#include "TopoCentricPolarCRD.hpp"
#include "../../Log/Log.hpp"

namespace ns_Position
{
    using namespace ns_Log;

    class CRDFactory
    {
    public:
        using CRDPtr = CoordinateSys::Ptr;
        enum class ValueType
        {
            CartesianCRDSys,
            GeodeticCRDSys,
            TopoCentricCRDSys,
            TopoCentricPolarCRDSys
        };

    public:
        template <class... Args>
        static CRDPtr MakeCRDPtr(ValueType type, Args &&...args)
        {
            switch (type)
            {
            case ValueType::CartesianCRDSys:
                if constexpr ((sizeof...(args) == 0 || sizeof...(args) == 1 || sizeof...(args) == 3) && std::is_constructible<CartesianCRD, Args...>::value)
                    return std::make_shared<CartesianCRD>(std::forward<Args>(args)...);
                break;
            case ValueType::GeodeticCRDSys:
                if constexpr ((sizeof...(args) == 0 || sizeof...(args) == 1 || sizeof...(args) == 3) && std::is_constructible<GeodeticCRD, Args...>::value)
                    return std::make_shared<GeodeticCRD>(std::forward<Args>(args)...);
                break;
            case ValueType::TopoCentricCRDSys:
                if constexpr ((sizeof...(args) == 1 || sizeof...(args) == 4) && std::is_constructible<TopoCentricCRD, Args...>::value)
                    return std::make_shared<TopoCentricCRD>(std::forward<Args>(args)...);
                break;
            case ValueType::TopoCentricPolarCRDSys:
                if constexpr ((sizeof...(args) == 1 || sizeof...(args) == 4) && std::is_constructible<TopoCentricPolarCRD, Args...>::value)
                    return std::make_shared<TopoCentricPolarCRD>(std::forward<Args>(args)...);
                break;
            }

            return nullptr;
        }

        static bool SwitchTo(ValueType switchTo, const CRDPtr &source, CRDPtr *out, const CRDPtr &central)
        {
            CRDPtr ans;
            CartesianCRD centralOfCartesian;

            if (central != nullptr)
                central->ToCartesianCRD(&centralOfCartesian);

            switch (switchTo)
            {
            case ValueType::CartesianCRDSys:
                ans = std::make_shared<CartesianCRD>();
                break;
            case ValueType::GeodeticCRDSys:
                ans = std::make_shared<GeodeticCRD>();
                break;
            case ValueType::TopoCentricCRDSys:
                ans = std::make_shared<TopoCentricCRD>(centralOfCartesian);
                break;
            case ValueType::TopoCentricPolarCRDSys:
                ans = std::make_shared<TopoCentricPolarCRD>(centralOfCartesian);
                break;
            default:
                *out = nullptr;
                return false;
            }

            CartesianCRD tmp;

            if (source->ToCartesianCRD(&tmp) && ans->FromCartesianCRD(tmp))
            {
                *out = ans;
                return true;
            }

            *out = nullptr;
            return false;
        }
    };
}